﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WMM.Tapioca.Linkbase;
using System.Xml.Linq;
using WMM.Tapioca.Instance;
using Saxon.Api;
using WMM.Tapioca.Taxonomy;
using WMM.Tapioca.Utilities;
using System.Collections;

namespace WMM.Tapioca.Formula
{
    /// <summary>
    /// A consistency assertion is a statement of expectations in relation to the consistency of facts in an input XBRL
    /// instance with the values that can be derived for those facts from the same XBRL instance by processing formulae.
    /// </summary>
    public class ConsistencyAssertion : FormulaResource
    {
        protected static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        protected DTS dts;
        protected Dictionary<string, string> namespaces = new Dictionary<string, string>();
        protected string nsvariable = "http://xbrl.org/2008/formula/conformance/variable";

        private Message messageSatisfied;

        /// <summary>
        /// Arcrole identifying that the message is associated with assertion success.
        /// arcrole="http://xbrl.org/arcrole/2010/assertion-satisfied-message".
        /// </summary>
        public Message MessageSatisfied
        {
            get { return messageSatisfied; }
            set { messageSatisfied = value; }
        }

        private Message messageUnsatisfied;

        /// <summary>
        /// Arcrole identifying that the message is associated with assertion unsuccessful.
        /// arcrole="http://xbrl.org/arcrole/2010/assertion-unsatisfied-message".
        /// </summary>
        public Message MessageUnsatisfied
        {
            get { return messageUnsatisfied; }
            set { messageUnsatisfied = value; }
        }

        private bool strict;

        /// <returns>An attribute, @strict, specifies whether the consistency assertion is 
        /// evaluated for produced output facts when there is no matching input fact. </returns>
        public bool Strict
        {
            get { return strict; }
            set { strict = value; }
        }

        private string absoluteAcceptanceRadius;

        /// <returns> Content of attribute "use". </returns>
        public string AbsoluteAcceptanceRadius
        {
            get { return absoluteAcceptanceRadius; }
            set { absoluteAcceptanceRadius = value; }
        }

        private string proportionalAcceptanceRadius;

        /// <returns> Content of attribute "use". </returns>
        public string ProportionalAcceptanceRadius
        {
            get { return proportionalAcceptanceRadius; }
            set { proportionalAcceptanceRadius = value; }
        }

        Precondition precondition;

        /// <returns> List of Fact Variables. </returns>
        public Precondition PreconditionTest
        {
            get { return precondition; }
            set { precondition = value; }
        }

        List<Arc> assertionToMessageArcs;

        /// <returns> List of arcs Assertion to Message. </returns>
        public List<Arc> AssertionToMessageArcs
        {
            get { return assertionToMessageArcs; }
            set { assertionToMessageArcs = value; }
        }


        private List<FormulaXBRL> formulasXbrl;

        /// <returns> List of arcs Assertion to Message. </returns>
        public List<FormulaXBRL> FormulasXbrl
        {
            get { return formulasXbrl; }
            set { formulasXbrl = value; }
        }

        private Resource labelAssertion;

        /// <returns> List of VariableSet. </returns>
        public Resource LabelAssertion
        {
            get { return labelAssertion; }
            set { labelAssertion = value; }
        }

        private string elementLabel;

        /// <returns> The Element Label. </returns>
        public string ElementLabel
        {
            get { return elementLabel; }
            set { elementLabel = value; }
        }

        List<Parameter> parameters;

        /// <returns> List of parameters. </returns>
        public List<Parameter> Parameters
        {
            get { return parameters; }
            set { parameters = value; }
        }

        List<VariableSet> variableSetArcs;

        /// <returns> List of VariableSet. </returns>
        public List<VariableSet> VariableSetArcs
        {
            get { return variableSetArcs; }
            set { variableSetArcs = value; }
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        public ConsistencyAssertion(Linkbase.Resource resourceAssertion, Dictionary<string, string> namespaces)
            : base(resourceAssertion)
        {
            assertionToMessageArcs = new List<Arc>();
            formulasXbrl = new List<FormulaXBRL>();

            Parameters = new List<Parameter>();            
            variableSetArcs = new List<VariableSet>();

            // get the formula resource
            foreach (Arc arc in resourceAssertion.ExtendedLinkParent.Arcs)
            {
                if (arc.FromId.Equals(Label) && arc.ArcRole.AbsoluteUri.Equals("http://xbrl.org/arcrole/2008/consistency-assertion-formula"))
                {
                    List<Resource> resources = GetResources(arc.ToId, resourceAssertion.ExtendedLinkParent);

                    foreach (Resource resource in resources)
                    {
                        if (resource.LocalName.Equals("formula"))
                        {
                            formulasXbrl.Add(new FormulaXBRL(resource, resourceAssertion.ExtendedLinkParent.ParentDocument, namespaces));
                        }
                    }
                }
            }

            foreach (Arc arc in resourceAssertion.ExtendedLinkParent.Arcs)
            {
                if (arc.FromId.Equals(Label) &&  arc.ArcRole.AbsoluteUri.Equals("http://xbrl.org/arcrole/2008/consistency-assertion-parameter"))
                {
                    VariableSetArcs.Add(new VariableSet(arc));

                    List<Resource> resources = GetResources(arc.ToId, resourceAssertion.ExtendedLinkParent);

                    foreach (Resource resource in resources)
                    {
                        if (resource.LocalName.Equals("parameter"))
                        {
                            Parameters.Add(new Parameter(resource));
                        }
                    }
                }

                if (arc.FromId.Equals(Label) && arc.ArcRole.AbsoluteUri.Equals("http://xbrl.org/arcrole/2008/variable-set"))
                {
                    VariableSetArcs.Add(new VariableSet(arc));

                    List<Resource> resources = GetResources(arc.ToId, resourceAssertion.ExtendedLinkParent);

                    foreach (Resource resource in resources)
                    {
                        if (resource.LocalName.Equals("factVariable") || resource.LocalName.Equals("generalVariable"))
                        {
                            log.Error("Error: The consistency assertion-: " + this.Label + " . The consistency assertion has a consistency-assertion-parameter relationship to a fact variable or to a general variable.");
                            //dts.ValidatedFormula = false;
                            //return;
                        }
                    }
                }
            }

            foreach (XAttribute attr in resourceAssertion.ResourceXElement.Attributes())
            {
                if (attr.Name.LocalName.Equals("strict"))
                {
                    strict = Boolean.Parse(attr.Value);
                }

                if (attr.Name.LocalName.Equals("absoluteAcceptanceRadius"))
                {
                    absoluteAcceptanceRadius = attr.Value;
                }

                if (attr.Name.LocalName.Equals("proportionalAcceptanceRadius"))
                {
                    proportionalAcceptanceRadius = attr.Value;
                }

                if (attr.Name.LocalName.Equals("id"))
                {
                    Id = attr.Value;
                }
            }           

            foreach (Arc arc in resourceAssertion.ExtendedLinkParent.Arcs)
            {
                if (arc.FromId.Equals(Label) &&
                    arc.ArcRole.AbsoluteUri.Equals("http://xbrl.org/arcrole/2010/assertion-satisfied-message"))
                {
                    AssertionToMessageArcs.Add(arc);
                    Linkbase.Resource resource = GetResource(arc.ToId, resourceAssertion.ExtendedLinkParent);
                    if (resource.LocalName.Equals("message"))
                    {
                        MessageSatisfied = new Message(resource);
                    }
                }

                if (arc.FromId.Equals(Label) &&
                    arc.ArcRole.AbsoluteUri.Equals("http://xbrl.org/arcrole/2010/assertion-unsatisfied-message"))
                {
                    AssertionToMessageArcs.Add(arc);
                    Linkbase.Resource resource = GetResource(arc.ToId, resourceAssertion.ExtendedLinkParent);

                    if (resource.LocalName.Equals("message"))
                    {
                        MessageUnsatisfied = new Message(resource);
                    }
                }
                

                if (arc.FromId.Equals(Label) && arc.ArcRole.AbsoluteUri.Equals("http://xbrl.org/arcrole/2008/variable-set-precondition"))
                {
                    Linkbase.Resource resource = GetResource(arc.ToId, resourceAssertion.ExtendedLinkParent);

                    if (resource.LocalName.Equals("precondition"))
                    {
                        PreconditionTest = new Precondition(resource);
                    }
                }

                if (arc.FromId.Equals(Label) && arc.ArcRole.AbsoluteUri.Equals("http://xbrl.org/arcrole/2008/element-label"))
                {
                    LabelAssertion = GetResource(arc.ToId, resourceAssertion.ExtendedLinkParent);
                }                
            }
            
        }


        /// <summary>
        /// Evaluating the assertion.
        /// </summary>
        public bool EvaluateAssertion(InstanceDocument instance, FormulaProcessor formulaProcessor)
        {
            int k = 1;

            bool evaluateResult = false;

            this.dts = instance.Dts;
            this.namespaces = dts.DtsFormula.Namespaces;

            foreach (FormulaXBRL formula in this.FormulasXbrl)
            {
                formula.dts = instance.Dts;
                formula.namespaces = dts.DtsFormula.Namespaces;

                // clear
                formula.variableItems.Clear();
                formula.variableAsSequenceEntries.Clear();
                formula.variableAsItem.Clear();
                formula.variableNamesAsItem.Clear();
                formula.parameterDictionary.Clear();
                formula.dependencyGraph = new Graph();

                // Declare variable 
                formula.DeclareVariables(formula, formula.compiler);

                XPathSelector selector = formula.compiler.Compile(formula.Value).Load();

                formula.SetContextItem(FormulaXBRL.processor, selector, instance.ThisXDocument.Root.GetXmlNode());

                formula.BindParameters(selector, formula, formula.parameterDictionary);

                Dictionary<string, XdmAtomicValue> variableFallBack = new Dictionary<string, XdmAtomicValue>();

                // Evaluate variable set
                
                try
                {
                    formula.EvaluateFactVariables(formula.variableItems, formula.variableSequences, variableFallBack);
                }
                catch (Exception ex)
                {
                    log4net.GlobalContext.Properties["assertionId"] = this.Id + " (evaluation " + k + ")";
                    log4net.GlobalContext.Properties["satisfied"] = "Exception";
                    FormulaProcessor.log.Error("Assertion:" + this.Id + " message:" + ex.Message);
                    formulaProcessor.assertionsNotSatisfied++;
                    k = k + 1;
                    return false;
                }

                if (formula.variableItems.Count == 0 && formula.variableSequences.Count == 0)
                {
                    log4net.GlobalContext.Properties["satisfied"] = "Not Evaluated";
                    FormulaProcessor.log.Error("Assertion:" + this.Id + " message: return 0 elements");
                    formulaProcessor.assertionsNotSatisfied++;
                    return false;
                }

                if ((formula.variableItems.Count + formula.parameterDictionary.Count + variableFallBack.Count + formula.variableSequences.Count) < this.VariableSetArcs.Count)
                {
                    return false;
                }

                for (int i = 0; i < formula.variableItems.Count; i++)
                {
                    formula.variableAsItem.Add(formula.variableItems.ElementAt(i).Value);
                    formula.variableNamesAsItem.Add(formula.variableItems.ElementAt(i).Key);
                }

                List<List<FactItem>> cartesianProducts = ConvertToList(MyExtensions.CartesianProduct(formula.variableAsItem).ToList());

                foreach (List<FactItem> cartesianProductItem in cartesianProducts)
                {
                    Dictionary<string, FactItem> variableNotSequenceEntries = new Dictionary<string, FactItem>();

                    formula.variableAsSequenceEntries.Clear();

                    if (formula.ImplicitlyMatchItems(cartesianProductItem, formula.variableNamesAsItem, formula.variableSequences, formula))
                    {
                        // Bind Variable

                        formula.BindVariable(FormulaXBRL.processor, selector, cartesianProductItem, formula.variableNamesAsItem, formula.variableAsSequenceEntries, variableNotSequenceEntries, variableFallBack);

                        // Evaluate Precondition

                        Boolean precondition = EvaluatePrecondition(formula.compiler, variableNotSequenceEntries, formula);

                        if (!string.IsNullOrEmpty(this.Id))
                        {
                            log4net.GlobalContext.Properties["assertionId"] = this.Id + " (evaluation " + k + ")";
                        }
                        else
                        {
                            log4net.GlobalContext.Properties["assertionId"] = this.Label + " (evaluation " + k + ")";
                        }

                        formula.ProduceOutputFact(formulaProcessor, selector.EvaluateSingle(), cartesianProductItem, formula.variableItems, formula.variableSequences, variableNotSequenceEntries);

                        if (precondition)
                        {
                            // Evaluate the XPath expression                            

                            string source = formula.Source;

                            // Consistency assertions are tested by comparing the values of derived facts with the 
                            // values of aspect-matching facts in the assertion input for consistency. 

                            FactItem itemToCompare = null;
                            List<FactItem> aspectMatchedInputFacts = new List<FactItem>();

                            if (formula.ConceptAspectFormula != null)
                            {
                                if (formula.ConceptAspectFormula.Qname != null)
                                {
                                    Concept concept = dts.GetConceptByName(formula.ConceptAspectFormula.Qname.LocalName); 
                                    aspectMatchedInputFacts = dts.LocateItems(concept);                                    
                                }

                                else if (formula.ConceptAspectFormula.QnameExpression != null)
                                {
                                    string result = formula.EvaluateXPathExpression(formula, formula.ConceptAspectFormula.QnameExpression, variableNotSequenceEntries);
                                    Concept concept = dts.GetConceptByName(result.GetLocalName());
                                    aspectMatchedInputFacts = dts.LocateItems(concept);
                                }

                                foreach (FactItem aspectMatchedInputFact in aspectMatchedInputFacts)
                                {
                                    cartesianProductItem.Add(aspectMatchedInputFact);

                                    if (MatchingAspects(aspectMatchedInputFact, cartesianProductItem[0]))
                                    {
                                        itemToCompare = aspectMatchedInputFact;
                                        break;
                                    }
                                }
                            }
                            else // SAV
                            {
                                itemToCompare = variableNotSequenceEntries[source];
                                aspectMatchedInputFacts.Add(itemToCompare);
                            }                            

                            if (this.Strict && aspectMatchedInputFacts.Count == 0)
                            {
                                itemToCompare = null;
                            }

                            if (!this.Strict && aspectMatchedInputFacts.Count == 0) //|| formula.Precision == "0")
                            {
                                return false;
                            }

                            if (this.AbsoluteAcceptanceRadius != null && this.ProportionalAcceptanceRadius != null)
                            {
                                log.Error("Error: The consistency assertion: " + this.Label + " . The consistency assertion contains both an @absoluteAcceptanceRadius attribute and a @proportionalAcceptanceRadius attribute.");
                                dts.ValidatedFormula = false;
                                return false;
                            }

                            //if (this.Label == "assertion50")
                            //{
                            //    string tt = "teste";
                            //}

                            XdmItem resultFormula = selector.EvaluateSingle();

                            double formulaResult = 0;
                            bool isNumber = false;                            

                            if (resultFormula != null)
                            {
                                if (Double.TryParse(resultFormula.ToString(), out formulaResult))
                                {
                                    formulaResult = Round(formulaResult, formula, itemToCompare.Precision);
                                    isNumber = true;
                                }
                            }

                            if (this.AbsoluteAcceptanceRadius == null && this.ProportionalAcceptanceRadius == null)
                            {
                                if (itemToCompare != null)
                                {
                                    if (resultFormula != null)
                                    {
                                        if (isNumber)
                                        {
                                            double factInputValue = Round(itemToCompare.RoundedValue, formula, itemToCompare.Precision);
                                            if (formulaResult == (itemToCompare.Value == "" ? 0 : factInputValue))
                                            {
                                                evaluateResult = true;
                                            }
                                            else
                                            {
                                                dts.ValidatedFormula = false;
                                            }
                                        }
                                        else
                                        {
                                            if (resultFormula.ToString() == itemToCompare.Value)
                                            {
                                                evaluateResult = true;
                                            }
                                            else
                                            {
                                                dts.ValidatedFormula = false;
                                            }
                                        }
                                    }
                                    else
                                    {
                                        if (itemToCompare.IsNilSpecified)
                                        {
                                            evaluateResult = true;
                                        }
                                        else
                                        {
                                            dts.ValidatedFormula = false;
                                        }
                                    }
                                }
                            }

                            if (isNumber)
                            {
                                if (this.AbsoluteAcceptanceRadius != null)
                                {
                                    if (Math.Abs(formulaResult - itemToCompare.RoundedValue) <= Double.Parse(EvaluateExpression(this.AbsoluteAcceptanceRadius, formula, formulaResult)))
                                    {
                                         evaluateResult = true;
                                    }
                                    else
                                    {
                                        dts.ValidatedFormula = false;
                                    }
                                }

                                if (this.ProportionalAcceptanceRadius != null)
                                {
                                    if (Math.Abs(formulaResult - itemToCompare.RoundedValue) / itemToCompare.RoundedValue <= Double.Parse(EvaluateExpression(this.ProportionalAcceptanceRadius, formula, formulaResult)))
                                    {
                                        evaluateResult = true;
                                    }
                                    else
                                    {
                                        dts.ValidatedFormula = false;
                                    }
                                }
                            }
                            else
                            {
                                if (resultFormula.ToString() == itemToCompare.Value)
                                {
                                    evaluateResult = true;
                                }
                                else
                                {
                                    dts.ValidatedFormula = false;
                                }
                            }

                            BuildMessage(formulaProcessor, formula.compiler, formula.parameterDictionary, variableNotSequenceEntries, evaluateResult, formula, variableFallBack);
                        }
                    }                       
                }
            }

            return evaluateResult;
        }


        private bool MatchingAspects(FactItem item, FactItem itemToCompare)
        {
            if (item.Concept.SchemaElement.ElementSchemaType.IsNumeric())
            {
                if (!item.UnitEquals(itemToCompare))
                {
                    return false;
                }
            }

            if (!item.ContextEquals(itemToCompare))
            {
                return false;
            }

            return true;
        }


        protected bool EvaluatePrecondition(XPathCompiler compiler, Dictionary<string, FactItem> variableEntries, VariableSet formula)
        {
            XPathSelector selectorPrecondition = null;
            Boolean precondition = true;

            if (formula.Precondition != null)
            {
                string preconditionTest = formula.Precondition.Test;
                selectorPrecondition = compiler.Compile(preconditionTest).Load();

                foreach (string variableName in variableEntries.Keys)
                {
                    selectorPrecondition.SetVariable(new QName(nsvariable, variableName), new XdmAtomicValue(variableEntries[variableName].Value));
                }

                precondition = Boolean.Parse(selectorPrecondition.EvaluateSingle().ToString());
            }

            return precondition;
        }


        protected void BuildMessage(FormulaProcessor formulaProcessor, XPathCompiler compiler, Dictionary<string, string> parameterDictionary, Dictionary<string, FactItem> variableEntries, bool evaluateResult, VariableSet formula, Dictionary<string, XdmAtomicValue> variableFallBack)
        {
            if (this.ElementLabel == null)
            {
                foreach (Arc assertionToMessageArc in this.AssertionToMessageArcs)
                {
                    if (assertionToMessageArc.ArcRole.Equals("http://xbrl.org/arcrole/2010/assertion-satisfied-message"))
                    {
                        if (evaluateResult)
                        {
                            string message = formula.CompileMessage(this.MessageSatisfied, compiler, variableEntries, parameterDictionary, variableFallBack);
                            log4net.GlobalContext.Properties["satisfied"] = "yes";
                            FormulaProcessor.log.Info(message);
                            formulaProcessor.assertionsSatisfied++;
                        }
                    }

                    if (assertionToMessageArc.ArcRole.Equals("http://xbrl.org/arcrole/2010/assertion-unsatisfied-message"))
                    {
                        if (!evaluateResult)
                        {
                            string message = formula.CompileMessage(this.MessageUnsatisfied, compiler, variableEntries, parameterDictionary, variableFallBack);
                            log4net.GlobalContext.Properties["satisfied"] = "no";
                            FormulaProcessor.log.Error(message);
                            dts.ValidatedFormula = false;
                            formulaProcessor.assertionsNotSatisfied++;
                        }
                    }
                }
            }
            else
            {
                if (evaluateResult)
                {
                    log4net.GlobalContext.Properties["satisfied"] = "yes";
                    FormulaProcessor.log.Info(this.ElementLabel);
                    formulaProcessor.assertionsSatisfied++;
                }
                else
                {
                    log4net.GlobalContext.Properties["satisfied"] = "no";
                    FormulaProcessor.log.Error(this.ElementLabel);
                    dts.ValidatedFormula = false;
                    formulaProcessor.assertionsNotSatisfied++;
                }
            }

            if (this.ElementLabel == null && this.AssertionToMessageArcs.Count == 0)
            {
                if (evaluateResult)
                {
                    log4net.GlobalContext.Properties["satisfied"] = "yes";
                    FormulaProcessor.log.Info("This assertion(" + this.Label + ") evaluated true");
                    formulaProcessor.assertionsSatisfied++;
                }
                else
                {
                    log4net.GlobalContext.Properties["satisfied"] = "no";
                    FormulaProcessor.log.Error("This assertion(" + this.Label + ") evaluated false");
                    dts.ValidatedFormula = false;
                    formulaProcessor.assertionsNotSatisfied++;
                }
            }
        }


        protected string EvaluateExpression(string expression, FormulaXBRL formula, double formulaResult)
        {
            // Create a Processor instance.
            Processor processor = new Processor();

            //RegisterExtensionFunctions(processor);

            // Create the XPath expression.
            XPathCompiler compiler = processor.NewXPathCompiler();            
           
            // Declare variable 
            formula.DeclareVariables(formula, compiler);
            this.DeclareParameter(compiler);

            XPathSelector selector = compiler.Compile(expression).Load();

            Dictionary<string, string> parameterDictionary = new Dictionary<string, string>();

            this.BindParameters(selector, parameterDictionary);
            selector.ContextItem = new XdmAtomicValue(formulaResult);

            string result = selector.EvaluateSingle().ToString();

            return result;
        }


        internal void DeclareParameter(XPathCompiler compiler)
        {
            foreach (string prefix in namespaces.Keys)
            {
                compiler.DeclareNamespace(prefix, namespaces[prefix]);
            }

            foreach (VariableSet variableSet in VariableSetArcs)
            {                
                if (variableSet.Variable.Name.LocalName.Equals("parameter"))
                {
                    string prefix = variableSet.Name.GetPrefix();

                    if (prefix.Length > 0)
                    {
                        compiler.DeclareVariable(new QName(namespaces[prefix], variableSet.Name));
                    }
                    else
                    {
                        compiler.DeclareVariable(new QName(variableSet.Name));
                    }
                }
            }
        }


        internal void BindParameters(XPathSelector selector, Dictionary<string, string> parameterDictionary)
        {
            double value;

            foreach (VariableSet variable in VariableSetArcs)
            {
                foreach (Parameter parameter in Parameters)
                {
                    if (variable.To.Equals(parameter.Label))
                    {
                        string prefix = variable.Name.GetPrefix();

                        if (prefix.Length > 0)
                        {
                            if (double.TryParse(parameter.Value, out value))
                            {
                                selector.SetVariable(new QName(namespaces[prefix], variable.Name), new XdmAtomicValue(value));
                            }
                            else
                            {
                                selector.SetVariable(new QName(namespaces[prefix], variable.Name), new XdmAtomicValue(parameter.Value));
                            }
                        }
                        else
                        {
                            if (double.TryParse(parameter.Value, out value))
                            {
                                selector.SetVariable(new QName(variable.Name), new XdmAtomicValue(value));
                            }
                            else
                            {
                                selector.SetVariable(new QName(variable.Name), new XdmAtomicValue(parameter.Value));
                            }
                        }

                        log.Info("Formula trace: Parameter " + variable.Name + " bound value " + parameter.Value);
                        parameterDictionary.Add(variable.Name, parameter.Value);
                    }
                }
            }
        }


        protected static List<List<FactItem>> ConvertToList(List<IEnumerable<FactItem>> cartesianProductMembersAllClone)
        {
            List<List<FactItem>> list = new List<List<FactItem>>();

            foreach (IEnumerable<FactItem> cartesianProductMemberAll in cartesianProductMembersAllClone)
            {
                list.Add(cartesianProductMemberAll.ToList());
            }

            return list;
        }


        private Linkbase.Resource GetResource(string resourceLabel, ExtendedLink extendedLinkParent)
        {
            foreach (Linkbase.Resource resource in extendedLinkParent.Resources)
            {
                if (resource.Label.Equals(resourceLabel))
                {
                    return resource;
                }
            }

            return null;
        }


        private List<Resource> GetResources(string resourceLabel, ExtendedLink extendedLinkParent)
        {
            List<Resource> resources = new List<Resource>();

            foreach (Linkbase.Resource resource in extendedLinkParent.Resources)
            {
                if (resource.Label.Equals(resourceLabel))
                {
                    resources.Add(resource);
                }
            }

            return resources;
        }


        public double Round(double originalValue, FormulaXBRL formula, int precisionFactInput)
        {
            double roundedValue = originalValue;            

            if (formula.Decimals != null)
            {
                if (formula.Decimals == "INF")
                {
                    return roundedValue;
                }

                if (Convert.ToInt32(formula.Decimals) >= 0)
                {
                    roundedValue = Math.Round(roundedValue, Convert.ToInt32(formula.Decimals));
                }
                else
                {
                    double powerOfTen = Math.Pow(10.0, (double)(Math.Abs(Convert.ToInt32(formula.Decimals))));
                    roundedValue = roundedValue / powerOfTen;
                    roundedValue = Math.Round(roundedValue);
                    roundedValue = roundedValue * powerOfTen;
                }

                formula.Precision = InferPrecision(Convert.ToInt32(formula.Decimals), roundedValue).ToString();
            }

            if (formula.Precision != null)
            {
                if (formula.Precision == "INF")
                {
                    return roundedValue;
                }

                int[] array1 = { Convert.ToInt32(formula.Precision), precisionFactInput };

                int decimalInferred = InferDecimal(array1.Min(), roundedValue);

                if (decimalInferred >= 0)
                {
                    roundedValue = Math.Round(roundedValue, decimalInferred, MidpointRounding.AwayFromZero);
                }
                else
                {
                    double powerOfTen = Math.Pow(10.0, (double)(Math.Abs(decimalInferred)));
                    roundedValue = roundedValue / powerOfTen;
                    roundedValue = Math.Round(roundedValue);
                    roundedValue = roundedValue * powerOfTen;
                }
            }

            return roundedValue;
        }


        private int InferDecimal(int precision, double roundedValue)
        {
            if (precision == 0)
            {
                return 0;
            }
            else
            {
                int calculationPart1Value = GetNumberOfDigitsToLeftOfDecimalPoint(roundedValue);

                if (calculationPart1Value == 0)
                {
                    calculationPart1Value = GetNegativeNumberOfLeadingZerosToRightOfDecimalPoint(roundedValue);
                }

                int calculationPart2Value = GetExponentValue(roundedValue);

                return precision - calculationPart1Value + calculationPart2Value;
            }
        }


        private int InferPrecision(int decimalAspect, double roundedValue)
        {
            int calculationPart1Value = GetNumberOfDigitsToLeftOfDecimalPoint(roundedValue);

            if (calculationPart1Value == 0)
            {
                calculationPart1Value = GetNegativeNumberOfLeadingZerosToRightOfDecimalPoint(roundedValue);
            }

            int calculationPart2Value = GetExponentValue(roundedValue);
            int calculationPart3Value = decimalAspect;
            int precision = calculationPart1Value + calculationPart2Value + calculationPart3Value;

            if (precision < 0)
            {
                return 0;
            }
            else
            {
                return precision;
            }
        }


        private int GetNumberOfDigitsToLeftOfDecimalPoint(double roundedValue)
        {
            string[] parsedValue = ParseValueIntoComponentParts(roundedValue.ToString());
            string withoutLeadingZeros = parsedValue[0].TrimStart(new char[] { '0' });
            return withoutLeadingZeros.Length;
        }


        private string[] ParseValueIntoComponentParts(string originalValue)
        {
            string[] arrayToReturn = new string[3];

            string[] stringsAfterExponentSplit = originalValue.Split(new char[] { 'e', 'E' });

            if (stringsAfterExponentSplit.Length == 2)
            {
                arrayToReturn[2] = stringsAfterExponentSplit[1];
            }

            string numericValue = stringsAfterExponentSplit[0];
            string[] stringsAfterDecimalSplit = numericValue.Split(new char[] { '.' });
            arrayToReturn[0] = stringsAfterDecimalSplit[0].Replace("-", "");

            if (stringsAfterDecimalSplit.Length == 2)
            {
                arrayToReturn[1] = stringsAfterDecimalSplit[1];
            }

            return arrayToReturn;
        }


        private int GetNegativeNumberOfLeadingZerosToRightOfDecimalPoint(double roundedValue)
        {
            string[] parsedValue = ParseValueIntoComponentParts(roundedValue.ToString());
            string valueToTheRightOfTheDecimal = parsedValue[1];

            if (string.IsNullOrEmpty(valueToTheRightOfTheDecimal))
            {
                return 0;
            }

            int numberOfLeadingZeros = 0;
            int index = 0;

            while (index < valueToTheRightOfTheDecimal.Length)
            {
                if (valueToTheRightOfTheDecimal[index] == '0')
                {
                    numberOfLeadingZeros++;
                    index++;
                }
                else
                {
                    index = valueToTheRightOfTheDecimal.Length;
                    break;
                }
            }

            return -numberOfLeadingZeros;
        }


        private int GetExponentValue(double roundedValue)
        {
            string[] parsedValue = ParseValueIntoComponentParts(roundedValue.ToString());

            if (string.IsNullOrEmpty(parsedValue[2]))
            {
                return 0;
            }

            int exponentValue;
            bool success = int.TryParse(parsedValue[2], out exponentValue);

            if (success)
            {
                return exponentValue;
            }

            return 0;
        }
    }
}
